import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

import { Observable, BehaviorSubject } from 'rxjs';

import { CommonService } from './common.service';
import { GeneralConfig } from '../config/general.config';

/**
 * Authentication service which provides Observable variable.
 */
@Injectable()
export class AuthenticationService {
    // Stores the URL so we can redirect after logging in
    redirectUrl: string;

    // Using BehaviorSubject instead of Subject because:
    // - BehaviorSubject has the default value.
    // - First time the observor subscribes can get the current value immediately. Subject triggers in the next round.
    // - If the observor stays in a Service's constructor, it matters, because the service will be constructed only 
    //      when it is needed while the Observable might already has value. The service does not have to wait until next subscription happens.
    private isAuthenticatedSubject: BehaviorSubject<boolean> = new BehaviorSubject(false);
    private isScreenLockedSubject: BehaviorSubject<boolean> = new BehaviorSubject(false);
    private currentUserSubject: BehaviorSubject<any> = new BehaviorSubject(null);

    constructor(
        private commonService: CommonService,
        private router: Router
    ) {
        // Loads the user from session storage at the first time.
        let user = this.commonService.loadSessionData(GeneralConfig.localUserInfoStorageKey);

        // If user exists mean current has logged in.
        if (user) {
            this.isAuthenticatedSubject.next(true);
            this.currentUserSubject.next(user);
        }
    }

    get currentAuthenticationState(): boolean {
        return this.isAuthenticatedSubject.value;
    }

    get isAuthenticated$(): Observable<boolean> {
        return this.isAuthenticatedSubject.asObservable();
    }

    get currentScreenLockState(): boolean {
        return this.isScreenLockedSubject.value;
    }

    get isScreenLocked$(): Observable<boolean> {
        return this.isScreenLockedSubject.asObservable();
    }

    get currentUserState(): any {
        return this.currentUserSubject.value;
    }

    get currentUser$(): Observable<any> {
        return this.currentUserSubject.asObservable();
    }

    get needToCheckAuthorizationOnTable(): boolean {
        return this.commonService.loadSessionData(GeneralConfig.localUserInfoStorageKey).needToCheckAuthorizationOnTable;
    }

    getAuthorizationHeader(): string {
        return '';
    }

    /**
     * Notifies the observer that screen has locked.
     */
    lockScreen(): void {
        this.isScreenLockedSubject.next(true);
    }

    /**
     * Notifies the observer that screen has unlocked.
     */
    unlockScreen(): void {
        this.isScreenLockedSubject.next(false);
    }

    /**
     * Notifies the observer that user has logged out.
     * @param user The user object that will be sent to all observers.
     */
    saveUserInfo(user: any): void {
        this.commonService.saveSessionData(GeneralConfig.localUserInfoStorageKey, user);
        this.isAuthenticatedSubject.next(true);
        this.currentUserSubject.next(user);

        if (!this.commonService.isNullOrUndefinedOrEmptyString(this.redirectUrl)) {
            // Using navigateByUrl instead of navigate because navigate method will 
            // encode the '?' in the provided url string. It only accepts the url with params.
            this.router.navigateByUrl(this.redirectUrl);
        } else {
            this.router.navigate(['/home/product-introduction']);
        }
    }

    /**
     * Notifies the observer that user has logged out and clears the redirectUrl.
     */
    removeUserInfo(): boolean {
        try {
            this.commonService.removeSessionData(GeneralConfig.localUserInfoStorageKey);
            this.redirectUrl = null;
            this.isAuthenticatedSubject.next(false);
            this.currentUserSubject.next(null);

            return true;
        } catch (error) {
            return false;
        }
    }

    /**
     * Checks if current user has the authorization to access the specific table.
     * @param tableName The string of table name.
     * @returns True means user is authorized. False means user is NOT authorized.
     */
    checkAuthorization(tableName: string): boolean {
        // Gets current user authorized resources.
        let user = this.commonService.loadSessionData(GeneralConfig.localUserInfoStorageKey);

        // Checks if the specific table names exists in user's authorization.
        return user.userResource.filter(p => p.rs_cfg === tableName).length === 1;
    }

    /**
     * Checks if the session data exists. Triggers isAuthenticated$ as true if exists.
     */
    checkSessionDataExisting(): void {
        if (this.commonService.loadSessionData(GeneralConfig.localUserInfoStorageKey)) {
            this.isAuthenticatedSubject.next(true);
        }
    }
}